/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.model.security;

import java.util.logging.Level;
import org.apache.jackrabbit.core.security.authentication.AbstractLoginModule;
import org.apache.jackrabbit.core.security.authentication.Authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Credentials;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import offset.nodes.Constants;
import offset.nodes.server.model.XPathQuery;
import org.apache.jackrabbit.core.security.AnonymousPrincipal;
import org.apache.jackrabbit.core.security.principal.PrincipalProvider;

/**
 * This class implements a very simple authentication scheme based on passwords. User credentials must include a password, that will be matched
 * against the password of the user. 
 * 
 * The user password will be stored unencrypted in the repository, however on a branch different from the user. That way, access to the clear password
 * can be restricted based on user permissions.
 * 
 * @author Walter Luetgenau
 */
public class PasswordLoginModule extends AbstractLoginModule {

    private static Logger log = LoggerFactory.getLogger(PasswordLoginModule.class);
    private static Session systemSession;

    protected void doInit(CallbackHandler callbackHandler, Session session, Map options) throws LoginException {
        systemSession = session;
        log.debug("init: PassordLoginModule. Done.");
    }

    static Session getSystemSession() {
        return systemSession;
    }

    protected boolean impersonate(Principal principal, Credentials credentials) throws RepositoryException, LoginException {
        if (principal instanceof Group) {
            return false;
        }
        Subject impersSubject = getImpersonatorSubject(credentials);
        return impersSubject != null;
    }

    protected Authentication getAuthentication(Principal principal, Credentials creds) throws RepositoryException {
        if (principal instanceof Group) {
            return null;
        }
        // retrieve password using the uuid of the principal
        String password = getPassword(principal);
        // create authentication, that stores the password information to authenticate based on that
        return new PasswordAuthentication(password);
    }

    /**
     * Uses the configured {@link PrincipalProvider} to retrieve the principal.
     * It takes the {@link PrincipalProvider#getPrincipal(String)} for the User-ID
     * resolved by  {@link #getUserID(Credentials)}, assuming that
     * User-ID and the corresponding principal name are always identical.
     *
     * @param credentials
     * @return principal or <code>null</code> if the principal provider does
     * not contain a user-principal with the given userID/principal name.
     *
     * @see AbstractLoginModule#getPrincipal(Credentials) 
     */
    protected Principal getPrincipal(Credentials credentials) {
        try {
            if ( isAnonymous(credentials))
                return new AnonymousPrincipal();
            
            String userId = getUserID(credentials);
            // get user information from repository
            Node user = getUser(userId);
            if ( user == null)
                return null;
            
            return new UserNodePrincipal(user);
        } catch (RepositoryException ex) {
            java.util.logging.Logger.getLogger(PasswordLoginModule.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }
    
    protected String getPassword(Principal principal) throws RepositoryException {
        if ( !(principal instanceof UserNodePrincipal))
            return null;
        
        String reference = ((UserNodePrincipal)principal).getUser().getUUID();
        XPathQuery query = new XPathQuery(systemSession);
        NodeIterator it = query.executeQuery(
                "/jcr:root//element(*, " + Constants.TYPENAME_PASSWORD + ")[@" + Constants.PROP_USER_REFERENCE + "='"+ reference + "']");
        if ( !it.hasNext())
            return null;
        
        Node password = it.nextNode();
        return password.getProperty(Constants.PROP_USER_PASSWORD).getString();
    }
    
    protected Node getUser(String userId) throws RepositoryException {
        XPathQuery query = new XPathQuery(systemSession);
        NodeIterator it = query.executeQuery("/jcr:root//" + userId + "[@" + Constants.JCR_PRIMARY_TYPE + "='"+ Constants.TYPENAME_USER + "']");
        if ( !it.hasNext())
            return null;
        
        return it.nextNode();
    }
}
